/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.inflator;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.javaruntype.type.Type;
import org.jfab.core.collection.CGAnnotationList;
import org.jfab.core.collection.CGFieldList;
import org.jfab.core.collection.CGInterfaceList;
import org.jfab.core.collection.CGPropertyList;
import org.jfab.core.collection.CGTypeList;
import org.jfab.core.collection.CommentList;
import org.jfab.core.collection.ModifierList;
import org.jfab.core.factory.CGFieldFactory;
import org.jfab.core.factory.CGTypeFactory;
import org.jfab.core.factory.StringFactory;
import org.jfab.core.model.CGCompilationUnit;
import org.jfab.core.model.CGField;
import org.jfab.core.model.CGInterface;
import org.jfab.core.model.CGProperty;
import org.jfab.core.model.CGType;
import org.jfab.core.model.CGTypeUtilities;
import org.jfab.core.util.TypeParameterMap;

/**
 * Provides a partial implementation of a code generator compilation unit
 * inflator.
 * 
 * @param <T> Code generator object type.
 */
public abstract class CGCompilationUnitInflator<T extends CGCompilationUnit>
        implements Inflator<T>
{
    /** Code generator type factory. */
    private final CGTypeFactory _cgTypeFactory;

    /** Code generator type utilities. */
    private final CGTypeUtilities _cgTypeUtils;

    /** Field comparator. */
    private final Comparator<CGField> _fieldComparator;

    /** Field factory. */
    private final CGFieldFactory _fieldFactory;

    /** String factory. */
    private final StringFactory _stringFactory;

    /**
     * Construct this object with the given parameters.
     * 
     * @param stringFactory String factory. (required)
     * @param cgTypeUtils Code generator type utilities. (required)
     * @param cgTypeFactory Code generator type factory. (required)
     * @param fieldFactory Field factory. (required)
     * @param fieldComparator Field comparator. (required)
     */
    public CGCompilationUnitInflator(final StringFactory stringFactory,
            final CGTypeUtilities cgTypeUtils,
            final CGTypeFactory cgTypeFactory,
            final CGFieldFactory fieldFactory,
            final Comparator<CGField> fieldComparator)
    {
        Validate.notNull(stringFactory, "stringFactory is null");
        Validate.notNull(cgTypeUtils, "cgTypeUtils is null");
        Validate.notNull(cgTypeFactory, "cgTypeFactory is null");
        Validate.notNull(fieldFactory, "fieldFactory is null");
        Validate.notNull(fieldComparator, "fieldComparator is null");

        _stringFactory = stringFactory;
        _cgTypeUtils = cgTypeUtils;
        _cgTypeFactory = cgTypeFactory;
        _fieldFactory = fieldFactory;
        _fieldComparator = fieldComparator;
    }

    /**
     * @param object Code generator object.
     * @param superInterfaces Code generator interface list.
     * @param typeParameterMap Type parameter map.
     * 
     * @return new code generator fields for the given parameter.
     */
    protected CGFieldList createFields(final T object,
            final CGInterfaceList superInterfaces,
            final TypeParameterMap typeParameterMap)
    {
        final CGFieldFactory fieldFactory = getFieldFactory();
        final CGFieldList answer = new CGFieldList();

        if (object.isPropertyChangeSupported())
        {
            answer.add(fieldFactory.createPropertyChangeSupportField());
        }

        if (CollectionUtils.isNotEmpty(superInterfaces))
        {
            answer.addAll(createFields(superInterfaces,
                    object.isPropertyChangeSupported(), typeParameterMap));
        }

        if (CollectionUtils.isNotEmpty(object.getFields()))
        {
            final CGFieldList fields = new CGFieldList(object.getFields());

            for (final CGField field : fields)
            {
                final CGField cgField = createField(field, typeParameterMap);
                answer.add(cgField);
            }
        }

        Collections.sort(answer, getFieldComparator());

        return answer;
    }

    /**
     * @param cgObject Code generator object.
     * @param superInterfaces Code generator interface list.
     * 
     * @return a new type parameter map.
     */
    protected abstract TypeParameterMap createTypeParameterMap(T cgObject,
            final CGInterfaceList superInterfaces);

    /**
     * @return the cgTypeFactory
     */
    protected CGTypeFactory getCgTypeFactory()
    {
        return _cgTypeFactory;
    }

    /**
     * @return the cgTypeUtils
     */
    protected CGTypeUtilities getCgTypeUtils()
    {
        return _cgTypeUtils;
    }

    /**
     * @return the fieldComparator
     */
    protected Comparator<CGField> getFieldComparator()
    {
        return _fieldComparator;
    }

    /**
     * @return the fieldFactory
     */
    protected CGFieldFactory getFieldFactory()
    {
        return _fieldFactory;
    }

    /**
     * @return the stringFactory
     */
    protected StringFactory getStringFactory()
    {
        return _stringFactory;
    }

    /**
     * @param cgField Code generator field.
     * 
     * @return true if the given parameter has a getter method.
     */
    protected boolean isGetter(final CGField cgField)
    {
        return (cgField.isGetter() != null) && cgField.isGetter();
    }

    /**
     * @param cgField Code generator field.
     * 
     * @return true if the given parameter has a setter method.
     */
    protected boolean isSetter(final CGField cgField)
    {
        return (cgField.isSetter() != null) && cgField.isSetter();
    }

    /**
     * @param extendsType Extends type.
     * @param methodName Method name.
     * 
     * @return true if the given type has a method of the given name.
     */
    protected boolean isSuperClassMethod(final CGType extendsType,
            final String methodName)
    {
        boolean answer = false;

        if ((extendsType != null) && StringUtils.isNotEmpty(methodName))
        {
            final Type<?> type = _cgTypeUtils.getType(extendsType);

            if (type != null)
            {
                final Method[] methods = type.getRawClass().getMethods();

                if (!ArrayUtils.isEmpty(methods))
                {
                    for (int i = 0; !answer && (i < methods.length); i++)
                    {
                        if (methodName.equals(methods[i].getName()))
                        {
                            answer = true;
                        }
                    }
                }
            }
        }

        return answer;
    }

    /**
     * @param field Code generator field.
     * @param typeParameterMap Type parameter map.
     * 
     * @return a new code generator field.
     */
    private CGField createField(final CGField field,
            final TypeParameterMap typeParameterMap)
    {
        final StringFactory stringFactory = getStringFactory();
        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList(
                Collections.singletonList(stringFactory
                        .createCommentFromPropertyName(stringFactory
                                .createPropertyName(field.getName()))));
        final CGAnnotationList annotations = null;
        final ModifierList modifiers = new ModifierList(field.getModifiers());

        final CGType fieldType = typeParameterMap == null ? field.getType()
                : typeParameterMap.mapType(field.getType());
        final String fieldName = field.getName();
        final String initialValue = field.getInitialValue();
        final Boolean isGetter = field.isGetter();
        final Boolean isSetter = field.isSetter();
        final Boolean isUsedInIdentity = field.isUsedInIdentity();

        return getFieldFactory().createField(referencedTypes, commentLines,
                annotations, modifiers, fieldType, fieldName, initialValue,
                isGetter, isSetter, isUsedInIdentity);
    }

    /**
     * @param property Property.
     * @param typeParameterMap Type parameter map.
     * 
     * @return a new code generator field.
     */
    private CGField createFieldFromProperty(final CGProperty property,
            final TypeParameterMap typeParameterMap)
    {
        final StringFactory stringFactory = getStringFactory();
        final CGTypeList referencedTypes = null;
        final CommentList commentLines = new CommentList(
                Collections.singletonList(stringFactory
                        .createCommentFromPropertyName(property.getName())));
        final CGAnnotationList annotations = null;
        final ModifierList modifiers = new ModifierList(property.getModifiers());

        final CGType propertyType = typeParameterMap == null ? property
                .getType() : typeParameterMap.mapType(property.getType());
        final String fieldName = stringFactory
                .createMemberVariableName(property.getName());
        final String initialValue = property.getInitialValue();
        final Boolean isGetter = property.isGetter();
        final Boolean isSetter = property.isSetter();
        final Boolean isUsedInIdentity = property.isUsedInIdentity();

        return getFieldFactory().createField(referencedTypes, commentLines,
                annotations, modifiers, propertyType, fieldName, initialValue,
                isGetter, isSetter, isUsedInIdentity);
    }

    /**
     * @param superInterfaces Super interface list.
     * @param isPropertyChangeSupported Flag indicating if property changes are
     *            supported.
     * @param typeParameterMap Type parameter map.
     * 
     * @return new code generator fields for the given parameter.
     */
    private CGFieldList createFields(final CGInterfaceList superInterfaces,
            final boolean isPropertyChangeSupported,
            final TypeParameterMap typeParameterMap)
    {
        final CGFieldList answer = new CGFieldList();

        for (final CGInterface cgInterface : superInterfaces)
        {
            final List<CGProperty> properties = cgInterface.getProperties();

            if (CollectionUtils.isNotEmpty(properties))
            {
                if (isPropertyChangeSupported
                        || cgInterface.isPropertyChangeSupported())
                {
                    final CGFieldFactory fieldFactory = getFieldFactory();
                    answer.addAll(fieldFactory
                            .createPropertyChangeConstantFields(new CGPropertyList(
                                    properties)));
                }

                for (final CGProperty property : properties)
                {
                    final CGField cgField = createFieldFromProperty(property,
                            typeParameterMap);

                    answer.add(cgField);
                }
            }
        }

        return answer;
    }
}
